﻿using System;
using System.Collections.Generic;
using SurveySystem.Model;
using SurveySystem.Repository;

namespace SurveySystem.Factory
{
    /// <summary>
    /// Factory class providing utility methods for working with <see cref="Survey"/> objects.
    /// </summary>
    public class SurveyFactory
    {
        private ISurveyRepository surveyRepository;
        private ISurveyQuestionRepository questionRepository;
        private ISurveyAnswerRepository answerRepository;

        public SurveyFactory(ISurveyRepository surveyRepository, ISurveyQuestionRepository questionRepository, ISurveyAnswerRepository answerRepository)
        {
            this.surveyRepository = surveyRepository;
            this.questionRepository = questionRepository;
            this.answerRepository = answerRepository;
        }

        /// <summary>
        /// Pull in a Survey from the given repository and populate its questions and answers
        /// </summary>
        /// <param name="surveyID"></param>
        /// <returns></returns>
        public Survey GetPopulatedSurveyByID(int surveyID)
        {

            // get the survey from the survey repository
            Survey s = surveyRepository.GetSurveyByID(surveyID);

            PopulateSurvey(s);

            return s;
        }

        /// <summary>
        /// Populate a survey's questions and answers from the given repositories
        /// </summary>
        /// <param name="surveyID"></param>
        /// <returns></returns>
        public void PopulateSurvey(Survey s)
        {
            // survey not found, return null
            if (s == null)
                return;

            // empty the questions list
            s.Questions.Clear();

            // get the questions from the question repository
            List<Question> questions = questionRepository.GetQuestionsForSurvey(s.SurveyID);

            // loop through each question
            foreach (Question q in questions)
            {
                // get the possible answers from the answer repository
                List<Answer> answers = answerRepository.GetAnswersForQuestion(q.QuestionID);

                // add the answers to the question
                q.PossibleAnswers.AddRange(answers);

                // add the question to the survey
                s.Questions.Add(q);
            }

        }

        /// <summary>
        /// Creates a new survey and relevant questions and answers in the given repositories
        /// </summary>
        /// <param name="s"></param>
        public void AddSurvey(Survey s)
        {
            // add the survey
            surveyRepository.AddSurvey(s);

            // loop thru each question
            foreach (Question q in s.Questions)
            {
                // update ID field
                q.SurveyID = s.SurveyID;

                // add the question
                questionRepository.AddQuestion(q);

                // loop thru the question's answers
                foreach (Answer a in q.PossibleAnswers)
                {
                    // update ID field
                    a.QuestionID = q.QuestionID;

                    // add the answer
                    answerRepository.AddAnswer(a);
                }
            }

        }

        /// <summary>
        /// Updates a survey, its questions and answers in the specified repositories. 
        /// 
        /// The method first gets a fresh copy of the survey from the repositories, then compares questions and answers from 
        /// old and new to come up with a changelist, then executes that changelist
        /// </summary>
        /// <param name="s"></param>
        public void UpdateSurvey(Survey s)
        {
            Survey newSurvey = s;
            Survey oldSurvey = GetPopulatedSurveyByID(s.SurveyID);


            List<Question> oldQuestions = new List<Question>();
            List<Question> newQuestions = new List<Question>();

            List<Answer> oldAnswers = new List<Answer>();
            List<Answer> newAnswers = new List<Answer>();


            // parse the old survey for questions and answers
            foreach (Question q in oldSurvey.Questions)
            {
                oldQuestions.Add(q);

                foreach (Answer a in q.PossibleAnswers)
                {
                    oldAnswers.Add(a);
                }
            }
            // parse the new survey for questions and answers
            foreach (Question q in newSurvey.Questions)
            {

                // ensure the Question's SurveyID is the same as the Survey's SurveyID
                q.SurveyID = s.SurveyID;

                newQuestions.Add(q);

                // if this is a new question skip the next bit
                if (q.QuestionID != -1)
                {
                    foreach (Answer a in q.PossibleAnswers)
                    {
                        // ensure the answer's QuestionID is the same as the question's QuestionID
                        a.QuestionID = q.QuestionID;

                        newAnswers.Add(a);
                    }
                }
            }

            List<Question> qsToDelete = new List<Question>();
            List<Question> qsToUpdate = new List<Question>();
            List<Question> qsToAdd = new List<Question>();

            // look for Qs in old, but not in new, and mark for delete
            foreach (Question q in oldQuestions)
            {
                bool found = false;

                foreach (Question q2 in newQuestions)
                {
                    if (q2.QuestionID == q.QuestionID)
                    {
                        found = true;
                        break;
                    }
                }

                if (found == false)
                    // not found, mark for delete
                    qsToDelete.Add(q);

            }
            // look for Qs in new, but not in old, and mark for add
            foreach (Question q in newQuestions)
            {
                bool found = false;

                foreach (Question q2 in oldQuestions)
                {
                    if (q2.QuestionID == q.QuestionID)
                    {
                        found = true;
                        break;
                    }
                }

                if (found == false)
                    // not found, mark for delete
                    qsToAdd.Add(q);
                else
                    // found in both, mark for update
                    qsToUpdate.Add(q);

            }

            List<Answer> asToDelete = new List<Answer>();
            List<Answer> asToUpdate = new List<Answer>();
            List<Answer> asToAdd = new List<Answer>();

            // look for As in old, but not in new, and mark for delete
            foreach (Answer a in oldAnswers)
            {
                bool found = false;

                foreach (Answer a2 in newAnswers)
                {
                    if (a2.AnswerID == a.AnswerID)
                    {
                        found = true;
                        break;
                    }
                }

                if (found == false)
                    // not found, mark for delete
                    asToDelete.Add(a);

            }
            // look for As in new, but not in old, and mark for add
            foreach (Answer a in newAnswers)
            {
                if (a.QuestionID == -1)
                    // skip, it's part of a new question and will be added separately
                    continue;

                bool found = false;

                foreach (Answer a2 in oldAnswers)
                {
                    if (a2.AnswerID == a.AnswerID)
                    {
                        found = true;
                        break;
                    }
                }

                if (found == false)
                    // not found, mark for delete
                    asToAdd.Add(a);
                else
                    // found in both, mark for update
                    asToUpdate.Add(a);


            }

            Console.WriteLine("SurveyFactory.UpdateSurvey():");
            Console.WriteLine("Updating survey " + newSurvey.SurveyID.ToString());
            Console.WriteLine("Questions to add: " + qsToAdd.Count.ToString());
            Console.WriteLine("Questions to update: " + qsToUpdate.Count.ToString());
            Console.WriteLine("Questions to delete: " + qsToDelete.Count.ToString());
            Console.WriteLine("Answers to add: " + asToAdd.Count.ToString());
            Console.WriteLine("Answers to update: " + asToUpdate.Count.ToString());
            Console.WriteLine("Answers to delete: " + asToDelete.Count.ToString());


            // update the survey
            surveyRepository.UpdateSurvey(newSurvey);

            // delete any stale questions and answers
            foreach (Question q in qsToDelete)
                questionRepository.DeleteQuestion(q.QuestionID);
            foreach (Answer a in asToDelete)
                answerRepository.DeleteAnswer(a.AnswerID);

            // update any Qs and As to be updated
            foreach (Question q in qsToUpdate)
                questionRepository.UpdateQuestion(q);
            foreach (Answer a in asToUpdate)
                answerRepository.UpdateAnswer(a);

            // add any Qs and As to be added
            foreach (Question q in qsToAdd)
            {
                questionRepository.AddQuestion(q);

                foreach (Answer a in q.PossibleAnswers)
                {
                    // update ID field
                    a.QuestionID = q.QuestionID;

                    answerRepository.AddAnswer(a);
                }

            }
            foreach (Answer a in asToAdd)
            {
                answerRepository.AddAnswer(a);
            }
        }

        /// <summary>
        /// Deletes a survey, its questions and answers from the specified repositories
        /// </summary>
        /// <param name="s"></param>
        public void DeleteSurvey(Survey s)
        {
            // add the survey
            surveyRepository.DeleteSurvey(s.SurveyID);

            // loop thru each question
            foreach (Question q in s.Questions)
            {
                // add the question
                questionRepository.DeleteQuestion(q.QuestionID);

                // loop thru the question's answers
                foreach (Answer a in q.PossibleAnswers)
                {

                    // add the answer
                    answerRepository.DeleteAnswer(a.AnswerID);
                }
            }

        }

    }
}
